const {
    get_file_list,
    get_full_path,
    format_bytes,
} = require("../utils/file_tool");
const { success, getPrint, info, warn, whisper } = require("../utils/log_tool");
const { BaseCommand } = require("./base");
const path = require("node:path");
const { progress } = require("../utils/display_tool");
const { createWriteStream, readFileSync } = require("node:fs");
const { getCache, setCache } = require("../utils/cache_tool");
const crypto = require("node:crypto");
const { queueAsync } = require("../utils/func_tool");

class AliOssCommand extends BaseCommand {
    async execute() {
        const config = this.selfData.config;
        const mode = this.selfData.mode;

        return await new Promise((resolve, reject) => {
            const OSS = require("ali-oss");
            // 初始化OSS客户端。请将以下参数替换为您自己的配置信息。
            const client = new OSS({
                region: config.region, // 示例：'oss-cn-hangzhou'，填写Bucket所在地域。
                accessKeyId: config.access_key_id, // 确保已设置环境变量OSS_ACCESS_KEY_ID。
                accessKeySecret: config.access_key_secret, // 确保已设置环境变量OSS_ACCESS_KEY_SECRET。
                bucket: config.bucket, // 示例：'my-bucket-name'，填写存储空间名称。
            });
            switch (mode) {
                case "DOWNLOAD": {
                    const src = this.selfData.src;
                    const dst = get_full_path(this.selfData.dst);
                    const timeout =
                        this.selfData.options?.timeout || 1000 * 60 * 2;
                    client
                        .get(src, dst, {
                            timeout,
                        })
                        .then((result) => {
                            resolve(result);
                        })
                        .catch((err) => {
                            reject(err);
                        });
                    break;
                }
                case "UPLOAD_FILE":
                case "UPLOAD": {
                    const dst = this.selfData.dst;
                    const src = get_full_path(this.selfData.src);
                    const timeout =
                        this.selfData.options?.timeout || 1000 * 60 * 2;
                    info("");
                    info(`开始上传 ${path.basename(src)} 到 ${dst}`);
                    client
                        .put(dst, src, {
                            timeout,
                        })
                        .then((result) => {
                            resolve(result);
                        })
                        .catch((err) => {
                            reject(err);
                        });
                    break;
                }
                case "UPLOAD_FILES": {
                    const dst_folder = this.selfData.dst;
                    const concurrency = this.selfData.options?.concurrency || 5;
                    const timeout =
                        this.selfData.options?.timeout || 1000 * 60 * 5;
                    const strategy = this.selfData.options?.strategy || "hash";
                    const base = this.selfData.base;
                    whisper(
                        `在 ${base} 中收集要上传的文件...`,
                        undefined,
                        true,
                    );
                    const srcs = get_file_list(
                        this.selfData.src,
                        undefined,
                        true,
                    );
                    let retryTimes = 0;
                    const maxRetryTimes = 3;
                    const cacheKey =
                        `ali_oss/${config.bucket}/${dst_folder}`.replace(
                            /\//g,
                            ".",
                        );
                    const cacheType = this.selfData.cache_type;
                    const cacheParam = this.selfData.cache_param;
                    let cachedFiles = null;
                    getCache(cacheKey, cacheType, cacheParam).then(
                        (_cachedFiles) => {
                            cachedFiles = _cachedFiles;
                            info(
                                `cacheKey=${cacheKey}, cacheType=${cacheType}, cacheParam=${cacheParam},cachedFiles=${JSON.stringify(cachedFiles).substring(0, 20)}`,
                            );
                            if (
                                !cachedFiles ||
                                !Object.keys(cachedFiles).length
                            ) {
                                let marker = null;
                                var objs = [];
                                async function list() {
                                    do {
                                        const result = await client.list({
                                            prefix: `${dst_folder}/`,
                                            marker: marker,
                                        });
                                        marker = result.nextMarker;
                                        objs.push.apply(objs, result.objects);
                                    } while (marker);
                                    return objs;
                                }
                                list().then((objs) => {
                                    cachedFiles = {};
                                    objs.forEach((obj) => {
                                        cachedFiles[obj.name] = obj.etag
                                            .replaceAll('"', "")
                                            .toLowerCase();
                                    });
                                    setCache(
                                        cacheKey,
                                        cachedFiles,
                                        cacheType,
                                        cacheParam,
                                    );
                                    uploadFiles(srcs);
                                });
                            } else {
                                uploadFiles(srcs);
                            }
                            function uploadFiles(srcs) {
                                const rawLength = srcs.length;
                                // 过滤文件
                                srcs = srcs.filter((src) => {
                                    const cachedValue =
                                        cachedFiles[
                                            path
                                                .join(
                                                    dst_folder,
                                                    path.relative(base, src),
                                                )
                                                .replace(/\\/g, "/")
                                        ];
                                    switch (strategy) {
                                        case "lazy": {
                                            if (cachedValue) {
                                                return false;
                                            }
                                            break;
                                        }
                                        case "hash": {
                                            const hash = crypto
                                                .createHash("md5")
                                                .update(readFileSync(src))
                                                .digest("hex");
                                            if (cachedValue === hash) {
                                                return false;
                                            }
                                            break;
                                        }
                                        case "force": {
                                            break;
                                        }
                                        default: {
                                            throw `Unkown Strategy ${strategy}`;
                                            break;
                                        }
                                    }
                                    return true;
                                });
                                info(`预处理完成!`, "\r");
                                info(
                                    `实际 ${srcs.length}, 忽略 ${rawLength - srcs.length}, 共计 ${rawLength}`,
                                );

                                const tasks = srcs.map((src) => {
                                    const dst = path
                                        .join(
                                            dst_folder,
                                            path.relative(base, src),
                                        )
                                        .replace(/\\/g, "/");
                                    return client.put.bind(client, dst, src, {
                                        timeout,
                                    });
                                });
                                queueAsync(
                                    tasks,
                                    concurrency,
                                    (done, total, index, ret) => {
                                        progress(done, total, {
                                            desc: `${path.relative(base, srcs[index])} ${ret ? (ret.err ? "上传失败!" : "上传成功!") : "上传中..."}`,
                                            depth: 0,
                                        });
                                    },
                                ).then((result) => {
                                    var retrySrcs = [];
                                    for (var i = 0; i < result.length; i++) {
                                        if (result[i].err) {
                                            retrySrcs.push(srcs[i]);
                                            warn(
                                                `上传文件 ${path.relative(base, srcs[i])} 失败: ${result[i].err}`,
                                            );
                                        } else {
                                            // 当前上传的，存储为 hash
                                            const dst = path
                                                .join(
                                                    dst_folder,
                                                    path.relative(
                                                        base,
                                                        srcs[i],
                                                    ),
                                                )
                                                .replace(/\\/g, "/");
                                            cachedFiles[dst] = crypto
                                                .createHash("md5")
                                                .update(readFileSync(srcs[i]))
                                                .digest("hex");
                                        }
                                    }
                                    if (retrySrcs.length > 0) {
                                        retryTimes++;
                                        if (retryTimes > maxRetryTimes) {
                                            setCache(
                                                cacheKey,
                                                cachedFiles,
                                                cacheType,
                                                cacheParam,
                                            );
                                            reject(
                                                `上传文件失败，重试次数超过 ${maxRetryTimes} 次。`,
                                            );
                                            return;
                                        }
                                        uploadFiles(retrySrcs);
                                    } else {
                                        setCache(
                                            cacheKey,
                                            cachedFiles,
                                            cacheType,
                                            cacheParam,
                                        );
                                        resolve(true);
                                    }
                                });
                            }
                        },
                    );
                    break;
                }
                default: {
                    resolve(false);
                    break;
                }
            }
        });
    }
}

module.exports = {
    AliOssCommand,
};
